/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tcloudclient.watchService;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

;

/**
 *
 * @author Tomas
 */
public class WatchService extends Thread {

    private List<WatchServiceEventInterface> observers;
    java.nio.file.WatchService watcher;
    private final Map<WatchKey, Path> keys;
    private boolean running;
    private String watchPath;
    Path myDir;

    public WatchService(String watchPath) {
        this.running = true;
        this.keys = new HashMap<WatchKey, Path>();
        this.watchPath = watchPath;
        try {
            myDir = Paths.get(watchPath);
            watcher = myDir.getFileSystem().newWatchService();
            observers = new ArrayList<WatchServiceEventInterface>();
            registerAll(myDir);
        } catch (IOException ex) {
            Logger.getLogger(WatchService.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                    throws IOException {
                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
        keys.put(key, dir);
    }

    @Override
    public void run() {
        //define a folder root       
        try {
            boolean rename = false;
            for (;;) {

                WatchKey watckKey = watcher.take();
                if (!running) {
                    return;
                }

                Path dir = keys.get(watckKey);

                List<WatchEvent<?>> events = watckKey.pollEvents();
                for (int i = 0; i < events.size(); i++) {
                    WatchEvent event = events.get(i);

                    // Context for directory entry event is the file name of entry
                    WatchEvent<Path> ev = cast(event);
                    Path name = ev.context();
                    Path child = dir.resolve(name);
                    String toString = child.toString();
                    String path = toString.substring(watchPath.length() + 1, toString.length());

                    if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                        if (!rename) {
                            try {
                                if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) {
                                    registerAll(child);
                                }
                            } catch (IOException x) {
                                // ignore to keep sample readbale
                            }
                            System.out.println("Created: " + path);
                            notifyAllObservers(path, WatchServiceChangeType.CREATE);
                        }
                        rename = false;
                    }
                    if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                        if ((i + 1) < events.size()) {
                            WatchEvent nextEvent = events.get(i + 1);
                            if (nextEvent.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                                rename = true;
                                ev = cast(nextEvent);
                                name = ev.context();
                                child = dir.resolve(name);
                                toString = child.toString();
                                String pathNext = toString.substring(watchPath.length() + 1, toString.length());
                                System.out.println("Rename from: " + path + " to: " + pathNext);
                                notifyAllObservers(path, WatchServiceChangeType.RENAME, pathNext);
                            }
                        }
                        if (!rename) {
                            System.out.println("Delete: " + path);
                            notifyAllObservers(path, WatchServiceChangeType.DELETE);
                        }
                    }
                    if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                        System.out.println("Modify: " + path);
                        notifyAllObservers(path, WatchServiceChangeType.MODIFY);
                    }
                }

                boolean valid = watckKey.reset();
                if (!valid) {
                    keys.remove(watckKey);

                    // all directories are inaccessible
                    if (keys.isEmpty()) {
                        break;
                    }
                }
            }
        } catch (InterruptedException ex) {
            Logger.getLogger(WatchService.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private synchronized void notifyAllObservers(String path, WatchServiceChangeType type) {
        notifyAllObservers(path, type, null);
    }

    private synchronized void notifyAllObservers(String path, WatchServiceChangeType type, String newName) {
        WatchServiceChangeData data;
        if (type == WatchServiceChangeType.RENAME) {
            data = new WatchServiceChangeData(type, path, newName);
        } else {
            data = new WatchServiceChangeData(type, path);
        }

        for (WatchServiceEventInterface observer : observers) {
            observer.event(data);
        }
    }

    public synchronized void addObserver(WatchServiceEventInterface observer) {
        observers.add(observer);
    }

    public synchronized void removeObserver(WatchServiceEventInterface observer) {
        observers.remove(observer);
    }

    public void stopWatching() {
        running = false;
    }

    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>) event;
    }
}
